package cn.com.easy.pay.alipay.app.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;

import cn.com.easy.exception.BusinessException;
import cn.com.easy.pay.alipay.union.config.AlipayUnionConfig;

import com.alipay.api.internal.util.AlipaySignature;

/**
 * 功能：支付宝app异步通知
 * 
 * @author nibili 2017年4月7日
 * 
 */
public abstract class AlipayAppNotifyBaseController {

	private Logger logger = LoggerFactory.getLogger(AlipayAppNotifyBaseController.class);

	/**
	 * 阿里配置参数
	 * 
	 * @return
	 * @author nibili 2017年4月1日
	 */
	public abstract AlipayUnionConfig getAlipayConfig() throws BusinessException;

	/**
	 * * 不要做异常处理，让控制器来做，控制器可以把结果返回给支付宝<br/>
	 * // 付款完成后，支付宝系统发送该交易状态通知<br/>
	 * 支付宝支付成功， 异步回调，业务回调方法<br/>
	 * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，<br/>
	 * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），<br/>
	 * 3、校验通知中的seller_id（或者seller_email
	 * )是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），<br/>
	 * 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。<br/>
	 * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。<br/>
	 * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
	 * 
	 * @param notifyParam
	 * @author nibili 2017年4月7日
	 */
	public abstract void notityTradeSuccessCallback(NotifyParam notifyParam) throws BusinessException;

	/**
	 * * 不要做异常处理，让控制器来做，控制器可以把结果返回给支付宝<br/>
	 * // 退款日期超过可退款期限后（如三个月可退款），支付宝系统发送该交易状态通知<br/>
	 * 支付宝支付完成， 异步回调，业务回调方法<br/>
	 * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，<br/>
	 * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），<br/>
	 * 3、校验通知中的seller_id（或者seller_email
	 * )是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），<br/>
	 * 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。<br/>
	 * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。<br/>
	 * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
	 * 
	 * @param notifyParam
	 * @author nibili 2017年4月7日
	 */
	public abstract void notityTradeFinishCallback(NotifyParam notifyParam) throws BusinessException;

	/**
	 * 异步回调<br/>
	 * https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.AVBHmT&
	 * treeId=204&articleId=105302&docType=1
	 * 
	 * @param request
	 * @param response
	 * @param notifyParam
	 * @author nibili 2017年4月7日
	 */
	@RequestMapping("/notify")
	public void notify(HttpServletRequest request, HttpServletResponse response, NotifyParam notifyParam) {
		try {
			// 获取支付宝POST过来反馈信息
			// 获取支付宝POST过来反馈信息
			Map<String, String> params = new HashMap<String, String>();
			@SuppressWarnings("unchecked")
			Map<String, String[]> requestParams = request.getParameterMap();
			for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
				String name = (String) iter.next();
				String[] values = (String[]) requestParams.get(name);
				String valueStr = "";
				for (int i = 0; i < values.length; i++) {
					valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
				}
				// 乱码解决，这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
				// valueStr = new String(valueStr.getBytes("ISO-8859-1"),
				// "gbk");
				params.put(name, valueStr);
			}
			// 切记alipaypublickey是支付宝的公钥，请去open.alipay.com对应应用下查看。
			// boolean AlipaySignature.rsaCheckV1(Map<String, String> params,
			// String
			// publicKey, String charset, String sign_type)
			boolean flag = AlipaySignature.rsaCheckV1(params, getAlipayConfig().getAlipay_public_key(), AlipayUnionConfig.CHARSET, getAlipayConfig().getSigntype());
			if (flag == true) {
				// 第五步：在步骤四验证签名正确后，必须再严格按照如下描述校验通知数据的正确性。

				// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
				// 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
				// 3、校验通知中的seller_id（或者seller_email)是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
				// 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。
				// 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。
				// 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
				// ////////////////////////////////////////////////////////////////////////////////////////
				// 请在这里加上商户的业务逻辑程序代码
				// ——请根据您的业务逻辑来编写程序（以下代码仅作参考）——
				if (StringUtils.equals(notifyParam.getTrade_status(), "TRADE_FINISHED") == true) {
					// 判断该笔订单是否在商户网站中已经做过处理
					// 如果没有做过处理，根据订单号（out_trade_no）在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
					// 请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
					// 如果有做过处理，不执行商户的业务程序
					notityTradeFinishCallback(notifyParam);
					// 注意：
					// 退款日期超过可退款期限后（如三个月可退款），支付宝系统发送该交易状态通知
				} else if (StringUtils.equals(notifyParam.getTrade_status(), "TRADE_SUCCESS") == true) {
					// 判断该笔订单是否在商户网站中已经做过处理
					// 如果没有做过处理，根据订单号（out_trade_no）在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
					// 请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
					// 如果有做过处理，不执行商户的业务程序
					notityTradeSuccessCallback(notifyParam);
					// 注意：
					// 付款完成后，支付宝系统发送该交易状态通知
				}

				// ——请根据您的业务逻辑来编写程序（以上代码仅作参考）——

				response.getOutputStream().print("success"); // 请不要修改或删除

			} else {
				throw new BusinessException("支付宝app支付验证失败");
			}
		} catch (BusinessException ex) {
			try {
				response.getOutputStream().print("failure");
			} catch (IOException e) {
				e.printStackTrace();
			}
			logger.info("支付宝app支付nofity业务代码异常", ex);
		} catch (Exception ex) {
			try {
				response.getOutputStream().print("failure");
			} catch (IOException e) {
				e.printStackTrace();
			}
			logger.info("支付宝app支付nofity异常", ex);
		}
	}

	/**
	 * 支付宝传递来的参数
	 * 
	 * @author nibili 2017年4月7日
	 * 
	 */
	public static class NotifyParam {
		/* ==================基本参数=============== */
		/** 通知时间 Date 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss。 */
		private String notify_time;
		/** 通知类型 String 通知的类型。 */
		private String notify_type;
		/** 通知校验ID String 通知校验ID。 */
		private String notify_id;
		/** 开发者的app_id */
		private String app_id;

		/** 编码格式 */
		private String charset;
		/** 接口版本 */
		private String version;

		/** 签名方式 String DSA、RSA、MD5三个值可选，必须大写。 */
		private String sign_type;
		/** 签名 String 请参见本文档“附录：签名与验签”。 */
		private String sign;
		/* ================业务参数============ */
		/** 支付宝交易号 */
		private String trade_no;
		/** 商户网站唯一订单号 */
		private String out_trade_no;
		/** 商户业务号 */
		private String out_biz_no;
		/** 买家支付宝账户号 */
		private String buyer_id;
		/** 买家支付宝账号 */
		private String buyer_logon_id;
		/** 卖家支付宝账户号 */
		private String seller_id;
		/** 卖家支付宝账号 */
		private String seller_email;
		/** 交易状态 */
		private String trade_status;
		/** 订单金额 */
		private String total_amount;
		/** 实收金额 */
		private String receipt_amount;

		/** 开票金额 */
		private String invoice_amount;
		/** 付款金额 */
		private String buyer_pay_amount;
		/** 集分宝金额 */
		private String point_amount;
		/** 总退款金额 */
		private String refund_fee;
		/** 商品名称 */
		private String subject;
		/** 商品描述 */
		private String body;
		/** 交易创建时间 */
		private String gmt_create;
		/** 交易付款时间 */
		private String gmt_payment;
		/** 退款时间 */
		private String gmt_refund;
		/** 交易关闭时间 */
		private String gmt_close;
		/** 支付金额信息 */
		private String fund_bill_list;
		/** 回传参数 */
		private String passback_params;
		/** 优惠券信息 */
		private String voucher_detail_list;

		/**
		 * get ==================基本参数===============
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getNotify_time() {
			return notify_time;
		}

		/**
		 * set ==================基本参数===============
		 * 
		 * @param notify_time
		 * @author nibili 2017年4月6日
		 */
		public void setNotify_time(String notify_time) {
			this.notify_time = notify_time;
		}

		/**
		 * get 通知类型String通知的类型。
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getNotify_type() {
			return notify_type;
		}

		/**
		 * set 通知类型String通知的类型。
		 * 
		 * @param notify_type
		 * @author nibili 2017年4月6日
		 */
		public void setNotify_type(String notify_type) {
			this.notify_type = notify_type;
		}

		/**
		 * get 通知校验IDString通知校验ID。
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getNotify_id() {
			return notify_id;
		}

		/**
		 * set 通知校验IDString通知校验ID。
		 * 
		 * @param notify_id
		 * @author nibili 2017年4月6日
		 */
		public void setNotify_id(String notify_id) {
			this.notify_id = notify_id;
		}

		/**
		 * get 开发者的app_id
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getApp_id() {
			return app_id;
		}

		/**
		 * set 开发者的app_id
		 * 
		 * @param 开发者的app_id
		 * @author nibili 2017年4月6日
		 */
		public void setApp_id(String app_id) {
			this.app_id = app_id;
		}

		/**
		 * get 签名方式StringDSA、RSA、MD5三个值可选，必须大写。
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getSign_type() {
			return sign_type;
		}

		/**
		 * set 签名方式StringDSA、RSA、MD5三个值可选，必须大写。
		 * 
		 * @param sign_type
		 * @author nibili 2017年4月6日
		 */
		public void setSign_type(String sign_type) {
			this.sign_type = sign_type;
		}

		/**
		 * get 签名String请参见本文档“附录：签名与验签”。
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getSign() {
			return sign;
		}

		/**
		 * set 签名String请参见本文档“附录：签名与验签”。
		 * 
		 * @param sign
		 * @author nibili 2017年4月6日
		 */
		public void setSign(String sign) {
			this.sign = sign;
		}

		/**
		 * get ==========
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getTrade_no() {
			return trade_no;
		}

		/**
		 * set ==========
		 * 
		 * @param trade_no
		 * @author nibili 2017年4月6日
		 */
		public void setTrade_no(String trade_no) {
			this.trade_no = trade_no;
		}

		/**
		 * get 商户网站唯一订单号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getOut_trade_no() {
			return out_trade_no;
		}

		/**
		 * set 商户网站唯一订单号
		 * 
		 * @param out_trade_no
		 * @author nibili 2017年4月6日
		 */
		public void setOut_trade_no(String out_trade_no) {
			this.out_trade_no = out_trade_no;
		}

		/**
		 * get 商户业务号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getOut_biz_no() {
			return out_biz_no;
		}

		/**
		 * set 商户业务号
		 * 
		 * @param out_biz_no
		 * @author nibili 2017年4月6日
		 */
		public void setOut_biz_no(String out_biz_no) {
			this.out_biz_no = out_biz_no;
		}

		/**
		 * get 买家支付宝账户号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getBuyer_id() {
			return buyer_id;
		}

		/**
		 * set 买家支付宝账户号
		 * 
		 * @param buyer_id
		 * @author nibili 2017年4月6日
		 */
		public void setBuyer_id(String buyer_id) {
			this.buyer_id = buyer_id;
		}

		/**
		 * get 买家支付宝账号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getBuyer_logon_id() {
			return buyer_logon_id;
		}

		/**
		 * set 买家支付宝账号
		 * 
		 * @param buyer_logon_id
		 * @author nibili 2017年4月6日
		 */
		public void setBuyer_logon_id(String buyer_logon_id) {
			this.buyer_logon_id = buyer_logon_id;
		}

		/**
		 * get 卖家支付宝账户号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getSeller_id() {
			return seller_id;
		}

		/**
		 * set 卖家支付宝账户号
		 * 
		 * @param seller_id
		 * @author nibili 2017年4月6日
		 */
		public void setSeller_id(String seller_id) {
			this.seller_id = seller_id;
		}

		/**
		 * get 卖家支付宝账号
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getSeller_email() {
			return seller_email;
		}

		/**
		 * set 卖家支付宝账号
		 * 
		 * @param seller_email
		 * @author nibili 2017年4月6日
		 */
		public void setSeller_email(String seller_email) {
			this.seller_email = seller_email;
		}

		/**
		 * get 交易状态
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getTrade_status() {
			return trade_status;
		}

		/**
		 * set 交易状态
		 * 
		 * @param trade_status
		 * @author nibili 2017年4月6日
		 */
		public void setTrade_status(String trade_status) {
			this.trade_status = trade_status;
		}

		/**
		 * get 订单金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getTotal_amount() {
			return total_amount;
		}

		/**
		 * set 订单金额
		 * 
		 * @param total_amount
		 * @author nibili 2017年4月6日
		 */
		public void setTotal_amount(String total_amount) {
			this.total_amount = total_amount;
		}

		/**
		 * get 实收金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getReceipt_amount() {
			return receipt_amount;
		}

		/**
		 * set 实收金额
		 * 
		 * @param receipt_amount
		 * @author nibili 2017年4月6日
		 */
		public void setReceipt_amount(String receipt_amount) {
			this.receipt_amount = receipt_amount;
		}

		/**
		 * get 开票金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getInvoice_amount() {
			return invoice_amount;
		}

		/**
		 * set 开票金额
		 * 
		 * @param invoice_amount
		 * @author nibili 2017年4月6日
		 */
		public void setInvoice_amount(String invoice_amount) {
			this.invoice_amount = invoice_amount;
		}

		/**
		 * get 付款金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getBuyer_pay_amount() {
			return buyer_pay_amount;
		}

		/**
		 * set 付款金额
		 * 
		 * @param buyer_pay_amount
		 * @author nibili 2017年4月6日
		 */
		public void setBuyer_pay_amount(String buyer_pay_amount) {
			this.buyer_pay_amount = buyer_pay_amount;
		}

		/**
		 * get 集分宝金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getPoint_amount() {
			return point_amount;
		}

		/**
		 * set 集分宝金额
		 * 
		 * @param point_amount
		 * @author nibili 2017年4月6日
		 */
		public void setPoint_amount(String point_amount) {
			this.point_amount = point_amount;
		}

		/**
		 * get 总退款金额
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getRefund_fee() {
			return refund_fee;
		}

		/**
		 * set 总退款金额
		 * 
		 * @param refund_fee
		 * @author nibili 2017年4月6日
		 */
		public void setRefund_fee(String refund_fee) {
			this.refund_fee = refund_fee;
		}

		/**
		 * get 商品名称
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getSubject() {
			return subject;
		}

		/**
		 * set 商品名称
		 * 
		 * @param subject
		 * @author nibili 2017年4月6日
		 */
		public void setSubject(String subject) {
			this.subject = subject;
		}

		/**
		 * get 商品描述
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getBody() {
			return body;
		}

		/**
		 * set 商品描述
		 * 
		 * @param body
		 * @author nibili 2017年4月6日
		 */
		public void setBody(String body) {
			this.body = body;
		}

		/**
		 * get 交易创建时间
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getGmt_create() {
			return gmt_create;
		}

		/**
		 * set 交易创建时间
		 * 
		 * @param gmt_create
		 * @author nibili 2017年4月6日
		 */
		public void setGmt_create(String gmt_create) {
			this.gmt_create = gmt_create;
		}

		/**
		 * get 交易付款时间
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getGmt_payment() {
			return gmt_payment;
		}

		/**
		 * set 交易付款时间
		 * 
		 * @param gmt_payment
		 * @author nibili 2017年4月6日
		 */
		public void setGmt_payment(String gmt_payment) {
			this.gmt_payment = gmt_payment;
		}

		/**
		 * get 退款时间
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getGmt_refund() {
			return gmt_refund;
		}

		/**
		 * set 退款时间
		 * 
		 * @param gmt_refund
		 * @author nibili 2017年4月6日
		 */
		public void setGmt_refund(String gmt_refund) {
			this.gmt_refund = gmt_refund;
		}

		/**
		 * get 交易关闭时间
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getGmt_close() {
			return gmt_close;
		}

		/**
		 * set 交易关闭时间
		 * 
		 * @param gmt_close
		 * @author nibili 2017年4月6日
		 */
		public void setGmt_close(String gmt_close) {
			this.gmt_close = gmt_close;
		}

		/**
		 * get 支付金额信息
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getFund_bill_list() {
			return fund_bill_list;
		}

		/**
		 * set 支付金额信息
		 * 
		 * @param fund_bill_list
		 * @author nibili 2017年4月6日
		 */
		public void setFund_bill_list(String fund_bill_list) {
			this.fund_bill_list = fund_bill_list;
		}

		/**
		 * get 回传参数
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getPassback_params() {
			return passback_params;
		}

		/**
		 * set 回传参数
		 * 
		 * @param passback_params
		 * @author nibili 2017年4月6日
		 */
		public void setPassback_params(String passback_params) {
			this.passback_params = passback_params;
		}

		/**
		 * get 优惠券信息
		 * 
		 * @return
		 * @author nibili 2017年4月6日
		 */
		public String getVoucher_detail_list() {
			return voucher_detail_list;
		}

		/**
		 * set 优惠券信息
		 * 
		 * @param voucher_detail_list
		 * @author nibili 2017年4月6日
		 */
		public void setVoucher_detail_list(String voucher_detail_list) {
			this.voucher_detail_list = voucher_detail_list;
		}

		/**
		 * get 编码格式
		 * 
		 * @return
		 * @author nibili 2017年4月7日
		 */
		public String getCharset() {
			return charset;
		}

		/**
		 * set 编码格式
		 * 
		 * @param charset
		 * @author nibili 2017年4月7日
		 */
		public void setCharset(String charset) {
			this.charset = charset;
		}

		/**
		 * get 接口版本
		 * 
		 * @return
		 * @author nibili 2017年4月7日
		 */
		public String getVersion() {
			return version;
		}

		/**
		 * set 接口版本
		 * 
		 * @param version
		 * @author nibili 2017年4月7日
		 */
		public void setVersion(String version) {
			this.version = version;
		}

	}
}
